package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io/fs"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
)

var PB_IN = "./pb/proto"
var PB_OUT = "./pb"

type ProtoInfo struct {
	MsgFiles []string
	RpcFiles []string

	MsgMap    map[int32]string
	RpcMsgMap map[int32]string

	MsgList    []string
	RpcList    []string
	PbMsgList  []string
	PbMsgFiles []string
}

func Cmd(cmdName string, args ...string) {
	var err error
	cmd := exec.Command(cmdName, args...)
	var (
		stdout bytes.Buffer
		stderr bytes.Buffer
	)

	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	fmt.Printf("%v %v \n", cmdName, args)
	err = cmd.Run()
	if err != nil {
		fmt.Println(err.Error() + ": " + stderr.String())
	}
	fmt.Println(stdout.String())
}

func writeFile(pathName string, fileName string, data string) error {
	fileName = path.Join(pathName, fileName)
	var file, err = os.OpenFile(fileName, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
	if err != nil {
		return err
	}

	defer file.Close()
	w := bufio.NewWriter(file) //创建新的 Writer 对象
	n, _ := w.WriteString(data)
	fmt.Printf("write file %v %d bytes \n", fileName, n)
	w.Flush()

	return nil
}

func FileForeach(fpath string, fn func(line string) error) {
	file, err := os.Open(fpath)
	if err != nil {
		fmt.Println(err)
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	scanner.Split(bufio.ScanLines)
	for scanner.Scan() {
		fn(scanner.Text())
	}
}

func LoadProto(pbSrc string) (*ProtoInfo, error) {
	var res = &ProtoInfo{
		MsgMap:    map[int32]string{},
		RpcMsgMap: map[int32]string{},
	}
	var err = filepath.Walk(pbSrc, func(path string, info fs.FileInfo, err error) error {
		if !info.IsDir() && strings.HasSuffix(info.Name(), ".proto") {
			fmt.Println("loading file = ", info.Name(), " path = ", path)
			if strings.HasPrefix(info.Name(), "rpc_") {
				res.RpcFiles = append(res.RpcFiles, path)
			} else {
				res.MsgFiles = append(res.MsgFiles, path)
			}
		}
		return nil
	})

	if err != nil {
		return nil, err
	}

	file_func := func(line string, rpc bool) error {
		reg := regexp.MustCompile("message[\\s\t]*[a-zA-Z_0-9]+")
		msg := reg.FindString(line)
		if msg != "" {
			msg = strings.TrimSpace(strings.TrimPrefix(msg, "message"))
			reg = regexp.MustCompile("cmd[\\s\t]*=[\\s\t]*[0-9]+")
			regStr := reg.FindString(line)
			if regStr != "" {
				regList := strings.Split(regStr, "=")
				if cmd, _ := strconv.Atoi(strings.Trim(regList[1], " ")); cmd > 0 {
					if rpc {
						res.RpcMsgMap[int32(cmd)] = strings.ToUpper(msg[:1]) + msg[1:]
					} else {
						res.MsgMap[int32(cmd)] = strings.ToUpper(msg[:1]) + msg[1:]
					}
				}
			}
		}
		return nil
	}

	for _, fpath := range res.MsgFiles {
		FileForeach(fpath, func(line string) error {
			file_func(line, false)
			return nil
		})
	}

	for _, fpath := range res.RpcFiles {
		FileForeach(fpath, func(line string) error {
			file_func(line, true)
			return nil
		})
	}

	return res, nil
}

func LoadProtoTag(pbSrc string) error {
	var err = filepath.Walk(pbSrc, func(path string, info fs.FileInfo, err error) error {
		if !info.IsDir() && info.Name() == "db.pb.go" {
			fmt.Println("deal file = ", info.Name(), " path = ", path)
			Cmd("protoc-go-inject-tag", []string{
				fmt.Sprintf("-input=%s", path),
			}...)
		}
		return nil
	})

	if err != nil {
		return fmt.Errorf("protoc go jnject tag error")
	}
	return nil
}

func GenerateInitGo(fpath string, res *ProtoInfo) {
	codeStr := "package pb\n\n"
	codeStr = codeStr + "import (\n\t\"reflect\"\n\t\"leaf/core/msg\"\n)\n\n"

	codeStr = codeStr + "var MapId2MessageType = map[uint16]reflect.Type{\n"
	for key, val := range res.MsgMap {
		codeStr = codeStr + "\t" + fmt.Sprintf("%v", key) + ":  reflect.TypeOf(&" + val + "{}),\n"
	}
	for key, val := range res.RpcMsgMap {
		codeStr = codeStr + "\t" + fmt.Sprintf("%v", key) + ":  reflect.TypeOf(&" + val + "{}),\n"
	}
	codeStr = codeStr + "}\n"
	codeStr = codeStr + "\nfunc init() {\n\tmsg.RegisterMessage(MapId2MessageType)\n}"
	writeFile(fpath, "init.go", codeStr)
}

func main() {
	Cmd("protoc", []string{
		fmt.Sprintf("--proto_path=%v", PB_IN),
		fmt.Sprintf("--go_out=./"),
		fmt.Sprintf("%v/*.proto", PB_IN),
	}...)

	res, err := LoadProto(PB_IN)
	if err != nil {
		fmt.Println(err)
		return
	}

	err = LoadProtoTag(PB_OUT)
	if err != nil {
		fmt.Println(err)
		return
	}

	GenerateInitGo(PB_OUT, res)
}
